<?php

set_time_limit(0);

class WebSocket{
    /**
     * socket连接池
     *
     * @var array
     */
    protected $_sockets = null;

    /**
     * 所有socket连接进来的信息，包括socket,client名字等
     *
     * @var array
     */
    protected $_users = null;

    /**
     * master socket(服务器的套接字)
     *
     * @var resource
     */
    protected $master = null;

    /**
     * WebSocket constructor.
     *
     * @param $address
     * @param $port
     *
     */
    public function __construct($protocal, $address, $port){
        //创建socket并把保存socket资源在$this->master
        $this->master = $this->WebSocket($protocal, $address, $port);
        $this->_sockets = array($this->master);
    }

    private function WebSocket($protocal, $address, $port){
        $socket = stream_socket_server("tcp://".$address.":".$port, $errno, $errstr);
        stream_set_blocking($socket, 0);
        if(!$socket)
            echo "$errstr ($errno)<br />\n";
        else
            return $socket;
    }

    public function run(){
         while(true){
            $changes = $this->_sockets;
            $write = NULL;
            $except = NULL;
            $timeout = 5;
            //为了阻塞程序继续往下执行,监听变化
            if(@stream_select($changes, $write, $except, $timeout)){
                foreach($changes as $socket){
                    if($socket == $this->master){
                        $client = stream_socket_accept($this->master);

                        $key = uniqid();
                        $this->_sockets[] = $client;//把新的连接存放进连接池
                        $this->_users[$key] = array(
                                'socket' => $client,//记录连接的sokcet信息
                                'handshake' => false//标志握手动作没有完成
                            );
                    }else{
                        $buffer = '';
                        //读取该socket信息
                        $buffer = @stream_socket_recvfrom($socket, 655350);
                        if(!$buffer)continue;

                        //find key of this socket
                        $key    = $this->search($socket);

                        if(!$this->checkClientStatus($key,$buffer)){
                            continue;
                        }

                        if(!$this->_users[$key]['handshake']){
                            //如果没有握手，则进行握手处理
                            $this->handshake($key, $buffer);
                        }else{
                            //decode data before send
                            $buffer = $this->decode($buffer);
                            if($buffer == false){
                                continue;
                            }
                            $this->send($key,$buffer);
                        }
                    }
                }
            }else{
                echo '123';
            }
        }
    }

    /**
     * find socket's unique id
     *
     * @param $socket
     * @return bool|int|string
     */
    private function search($socket){
        foreach($this->_users as $k=>$v){
            if($socket == $v['socket']){
                return $k;
            }
        }
        return false;
    }

    private function close($key){
        //close socket
        @fclose($this->_users[$key]['socket']);
        //delete record from users array
        unset($this->_users[$key]);
        //重新定义sockets连接池???
        $this->_sockets=array($this->master);
        if($this->_users){
            foreach($this->_users as $v){
                $this->sockets[]=$v['socket'];
            }
        }
        echo "===============\na socket closed\n===============\n";
        echo "A total of ".count($this->_users)." users online\n";

        $offline_msg = $this->encode(json_encode(
            array(
                'type'=>'offline',
                'key'=>$key,
                'online_nums'=>count($this->_users),
                'msg'=>"用户".$key."退出了聊天室")
            )
        );
        foreach ($this->_users as $user) {
            stream_socket_sendto($user['socket'],$offline_msg,strlen($offline_msg));
        }
    }

    /**
     * client-server handshake
     * @param string $k socket_id
     * @param string $buffer handshake data
     */
    private function handshake($k, $buffer){
        //截取Sec-WebSocket-Key的值并加密，其中$key后面的一部分258EAFA5-E914-47DA-95CA-C5AB0DC85B11字符串应该是固定的
        $buf = substr($buffer,strpos($buffer,'Sec-WebSocket-Key:')+18);
        $key = trim(substr($buf,0,strpos($buf,"\r\n")));
        $new_key = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));

        //按照协议组合信息进行返回
        $new_message = "HTTP/1.1 101 Switching Protocols\r\n";
        $new_message .= "Upgrade: websocket\r\n";
        $new_message .= "Sec-WebSocket-Version: 13\r\n";
        $new_message .= "Connection: Upgrade\r\n";
        $new_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n";
        stream_socket_sendto($this->_users[$k]['socket'],$new_message,strlen($new_message));
        //对已经握手的client做标志
        $this->_users[$k]['handshake'] = true;
        echo "A total of ".count($this->_users)." users online\n";
        return true;
    }

    //send message to client
    private function send($key,$buffer){
        parse_str($buffer,$msg);
        // 在线人数
        $msg['online_nums'] = count($this->_users);
        if(!array_key_exists('to', $msg)){
            // 走到这就说明浏览器异常关闭了
            // echo "jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj\n";
            // $str = socket_read($this->users[$key]['socket'], 1024);
            // socket_write($this->users[$key]['socket'],$str,strlen($str));
            $this->close($key);
        }else{
            if($msg['to']=='all'){
                //如果是上线消息的话
                if($msg['type'] == 'online'){
                    //将用户名和id一起绑定
                    $this->_users[$key]['username'] = $msg['username'];
                    //传递一个key值
                    $msg['key'] = $key;
                }
                //给所有客户端发送消息(除了自己)
                $queue = $this->_users;
                unset($queue[$key]);
                $str = $this->encode(json_encode($msg));
                foreach($queue as $user){
                    stream_socket_sendto($user['socket'],$str,strlen($str));
                }
            }
            if($msg['to']=='one'){
                //向指定的socket发送消息
                $key = $msg['to'];
                $str = $this->encode($msg['msg']);
                stream_socket_sendto($this->_users[$key]['socket'],$str,strlen($str));
            }
        }
        
    }

    //decode data frame
    function decode($received){
        $len = $masks = $data = $decoded = null;
        $buffer = $received;
        $len = ord($buffer[1]) & 127;
        if ($len === 126) {
           $masks = substr($buffer, 4, 4);
           $data  = substr($buffer, 8);
        } else {
           if ($len === 127) {
               $masks = substr($buffer, 10, 4);
               $data  = substr($buffer, 14);
           } else {
               $masks = substr($buffer, 2, 4);
               $data  = substr($buffer, 6);
           }
        }
        for ($index = 0; $index < strlen($data); $index++) {
           $decoded .= $data[$index] ^ $masks[$index % 4];
        }

        return $decoded;
    }

    //encode data frame
    function encode($msg){
        $frame = array(); 
        $frame[0] = '81'; 
        $len = strlen($msg);
        if($len < 126){
            $frame[1] = $len<16?'0'.dechex($len):dechex($len);
        }else if($len < 65025){
            $s=dechex($len);
            $frame[1]='7e'.str_repeat('0',4-strlen($s)).$s;
        }else{
            $s=dechex($len);
            $frame[1]='7f'.str_repeat('0',16-strlen($s)).$s;
        }
        $frame[2] = $this->ord_hex($msg);
        $data = implode('',$frame); 
        return pack("H*", $data); 
    }

    function ord_hex($data)  { 
        $msg = ''; 
        $l = strlen($data); 
        for ($i= 0; $i<$l; $i++) { 
            $msg .= dechex(ord($data{$i})); 
        } 
        return $msg; 
    }

    function checkClientStatus($key, $buffer){
        $recv_len = strlen($buffer);

        if($recv_len < 2){
            $this->close($key);
            return false;
        }else{
            // decode data frame header
            $firstbyte    = ord($buffer[0]);
            $secondbyte   = ord($buffer[1]);
            $data_len     = $secondbyte & 127;
            $is_fin_frame = $firstbyte >> 7;
            $masked       = $secondbyte >> 7;
            $opcode       = $firstbyte & 0xF;

            echo "opcode:$opcode\n\n";
            switch ($opcode){
                case 0x1:
                    break;
                case 0x2:
                    break;
                case 0x8:
                    $this->close($key);
                    return false;
                    break;
                case 0x9:
                    //ping frame
                    break;
                case 0xa:
                    //pong frame
                    break;
            }
        }
        return true;
    }
}
$WS = new WebSocket('tcp','192.168.100.142',8000);
$WS->run();